﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Fave;
using Vk.Methods.Users;

#endregion Using

namespace Vk.Methods.Groups
{
    /// <summary>
    ///     Группы
    /// </summary>
    public class Groups
    {
        private readonly Api _api;

        public Groups(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Позволяет добавлять ссылки в сообщество.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом, Вы должны быть администратором группы</remarks>
        /// <param name="groupId">Идентификатор сообщества, в которое добавляется ссылка </param>
        /// <param name="link">Адрес ссылки </param>
        /// <param name="text">Текст ссылки</param>
        /// <returns>В случае успешного выполнения возвращает обьект ссылки</returns>
        public async Task<LinkInfo> AddLink(int groupId, string link, string text = null)
        {
            if (link == null)
            {
                throw new ArgumentNullException(nameof(link));
            }

            var query = new ApiQuery("groups.addLink")
                .Add("group_id", groupId)
                .Add("link", link)
                .Add("text", text);

            var result = await _api.Manager.Execute(query);
            return new LinkInfo(result);
        }

        /// <summary>
        ///     Позволяет одобрить заявку в группу от пользователя.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом Вы должны быть администратором группы.</remarks>
        /// <param name="groupId">Идентификатор группы, заявку в которую необходимо одобрить. </param>
        /// <param name="userId">Идентификатор пользователя, заявку которого необходимо одобрить. </param>
        /// <returns>В случае успешного выполнения возвращает true</returns>
        public async Task<bool> ApproveRequest(int groupId, int userId)
        {
            var query = new ApiQuery("groups.approveRequest")
                .Add("group_id", groupId)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Добавляет пользователя в черный список группы.
        /// </summary>
        /// <param name="groupId">Идентификатор группы.</param>
        /// <param name="userId">Идентификатор пользователя, которого нужно добавить в черный список. </param>
        /// <param name="endDate">
        ///     Дата завершения срока действия бана в формате unixtime. Если параметр не указан пользователь
        ///     будет заблокирован навсегда.
        /// </param>
        /// <param name="reason">Причина бана</param>
        /// <param name="comment">Текст комментария к бану</param>
        /// <param name="commentVisible">Текст комментария будет отображаться пользователю</param>
        /// <returns>В случае успеха метод вернет true</returns>
        public async Task<bool> BanUser(int groupId,
                                        int userId,
                                        int? endDate = null,
                                        BanReason reason = null,
                                        string comment = null,
                                        bool? commentVisible = null)
        {
            var query = new ApiQuery("groups.banUser")
                .Add("group_id", groupId)
                .Add("user_id", userId)
                .Add("reason", reason.Value)
                .Add("end_date", endDate)
                .Add("comment", comment)
                .Add("comment_visible", commentVisible);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Создает новое сообщество.
        /// </summary>
        /// <param name="title">Название сообщества. </param>
        /// <param name="description">Описание сообщества, (не учитывается при type=public). </param>
        /// <param name="type">Тип создаваемого сообщества</param>
        /// <param name="publicCategory">???</param>
        /// <param name="subtype">Вид публичной страницы (только при type=public)</param>
        /// <returns>Объект, описывающий созданное сообщество.</returns>
        public async Task<Group> Create(string title,
                                        string description = null,
                                        ApiStringEnum<GroupType> type = null,
                                        int? publicCategory = null,
                                        PublicSubtype? subtype = null)
        {
            if (title == null)
            {
                throw new ArgumentNullException(nameof(title));
            }

            if (publicCategory != null)
            {
                if (type == null || type.Enumeration != GroupType.Page)
                {
                    throw new ArgumentOutOfRangeException(nameof(publicCategory),
                                                          "Public subcategory is available only when type == Page");
                }
            }

            var query = new ApiQuery("groups.get")
                .Add("title", title)
                .Add("description", description)
                .Add("type", type)
                .Add("public_category", publicCategory)
                .Add("subtype", (int) subtype.Value);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new Group(result)
                       : null;
        }

        /// <summary>
        ///     Позволяет удалить ссылки из сообщества.
        ///     Список ссылок может быть получен методом groups.getById, с параметром fields=links.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества, ссылки которого нужно удалить </param>
        /// <param name="linkId">Идентификатор ссылки, которую необходимо удалить</param>
        /// <returns>В случае успешного выполнения возвращает true</returns>
        public async Task<bool> DeleteLink(int groupId, int linkId)
        {
            var query = new ApiQuery("groups.deleteLink")
                .Add("group_id", groupId)
                .Add("link_id", linkId);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Редактирует сообщество.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        /// <param name="title">Название сообщества.</param>
        /// <param name="description">Описание сообщества.</param>
        /// <param name="screenName">Короткое имя сообщества. </param>
        /// <param name="access">Тип группы.</param>
        /// <param name="website">Адрес сайта, который будет указан в информации о группе. </param>
        /// <param name="subject">Тематика сообщества.</param>
        /// <param name="email">Электронный адрес организатора (для мероприятий). </param>
        /// <param name="phone">Номер телефона организатора (для мероприятий).</param>
        /// <param name="rss">Адрес rss для импорта новостей (доступен только группам, получившим соответствующее разрешение)</param>
        /// <param name="eventStartDate">Дата начала события. </param>
        /// <param name="eventFinishDate">Дата окончания события.</param>
        /// <param name="eventGroupId">Идентификатор группы, которая является организатором события (только для событий). </param>
        /// <param name="publicCategory">Категория публичной страницы.</param>
        /// <param name="publicSubcategory">Подкатегория публичной станицы.</param>
        /// <param name="publicDate">Дата основания компании, организации, которой посвящена публичная страница .</param>
        /// <param name="wall">Стена</param>
        /// <param name="topics">Обсуждения</param>
        /// <param name="photos">Фотографии.</param>
        /// <param name="video">Видеозаписи.</param>
        /// <param name="audio">Аудиозаписи. </param>
        /// <param name="links">Ссылки (доступно только для публичных страниц).</param>
        /// <param name="events">События (доступно только для публичных страниц). </param>
        /// <param name="places">Места (доступно только для публичных страниц). </param>
        /// <param name="contacts">Контакты (доступно только для публичных страниц).</param>
        /// <param name="docs">Документы сообщества..</param>
        /// <param name="wiki">Wiki-материалы сообщества. </param>
        /// <param name="messages">Cообщения сообщества.</param>
        /// <param name="ageLimits">Возрастное ограничение для сообщества. </param>
        /// <param name="market">Товары</param>
        /// <param name="marketComments">Комментарии к товарам.</param>
        /// <param name="marketCountry">Регионы доставки товаров. </param>
        /// <param name="marketCity">Города доставки товаров (в случае если указана одна страна). </param>
        /// <param name="marketCurrency">Валюта магазина.</param>
        /// <param name="marketContact">
        ///     Контакт для связи для продавцом.
        ///     Для использования сообщений сообщества следует включить их и передать значение 0.
        /// </param>
        /// <param name="marketWiki">Идентификатор wiki-страницы с описанием магазина. </param>
        /// <param name="obsceneFilter">Фильтр нецензурных выражений в комментариях. </param>
        /// <param name="obsceneStopwords">Фильтр по ключевым словам в комментариях. </param>
        /// <param name="obsceneWords">Ключевые слова для фильтра комментариев.</param>
        /// <returns>В случае успеха возвращает true.</returns>
        public async Task<bool> Edit(int groupId,
                                     string title = null,
                                     string description = null,
                                     string screenName = null,
                                     GroupAccess? access = null,
                                     string website = null,
                                     GroupSubject? subject = null,
                                     string email = null,
                                     string phone = null,
                                     string rss = null,
                                     int? eventStartDate = null,
                                     int? eventFinishDate = null,
                                     int? eventGroupId = null,
                                     int? publicCategory = null,
                                     int? publicSubcategory = null,
                                     DateTime? publicDate = null,
                                     int? wall = null,
                                     int? topics = null,
                                     int? photos = null,
                                     int? video = null,
                                     int? audio = null,
                                     bool? links = null,
                                     bool? events = null,
                                     bool? places = null,
                                     bool? contacts = null,
                                     int? docs = null,
                                     int? wiki = null,
                                     bool? messages = null,
                                     AgeLimit? ageLimits = null,
                                     bool? market = null,
                                     bool? marketComments = null,
                                     IEnumerable<int> marketCountry = null,
                                     IEnumerable<int> marketCity = null,
                                     MarketCurrency? marketCurrency = null,
                                     int? marketContact = null,
                                     int? marketWiki = null,
                                     bool? obsceneFilter = null,
                                     bool? obsceneStopwords = null,
                                     IEnumerable<string> obsceneWords = null)
        {
            var query = new ApiQuery("groups.edit")
                .Add("group_id", groupId)
                .Add("title", title)
                .Add("description", description)
                .Add("screen_name", screenName)
                .Add("access", (int) access.Value)
                .Add("website", website)
                .Add("email", email)
                .Add("phone", phone)
                .Add("rss", rss)
                .Add("event_start_date", eventStartDate)
                .Add("event_finish_date", eventFinishDate)
                .Add("event_group_id", eventGroupId)
                .Add("public_category", publicCategory)
                .Add("public_subcategory", publicSubcategory)
                .Add("public_date", publicDate)
                .Add("subject", (int?) subject)
                .Add("wall", wall)
                .Add("topics", topics)
                .Add("photos", photos)
                .Add("video", video)
                .Add("audio", audio)
                .Add("links", links)
                .Add("events", events)
                .Add("places", places)
                .Add("contacts", contacts)
                .Add("docs", docs)
                .Add("wiki", wiki)
                .Add("messages", messages)
                .Add("market", market)
                .Add("market_comments", marketComments)
                .Add("market_country", marketCountry)
                .Add("market_city", marketCity)
                .Add("age_limits", (int?) ageLimits)
                .Add("market_currency", (int?) marketCurrency)
                .Add("market_contact", marketContact)
                .Add("market_wiki", marketWiki)
                .Add("obscene_filter", obsceneFilter)
                .Add("obscene_stopwords", obsceneStopwords)
                .Add("obscene_words", obsceneWords);


            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет редактировать ссылки в сообществе.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества, в которое добавляется ссылка </param>
        /// <param name="linkId">Идентификатор редактируемой ссылки </param>
        /// <param name="text">Новое описание ссылки</param>
        /// <returns>В случае успешного выполнения возвращает true</returns>
        public async Task<bool> EditLink(int groupId, int linkId, string text)
        {
            if (text == null)
            {
                throw new ArgumentNullException(nameof(text));
            }

            var query = new ApiQuery("groups.editLink")
                .Add("group_id", groupId)
                .Add("link_id", linkId)
                .Add("text", text);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Позволяет назначить/разжаловать руководителя в сообществе или изменить уровень его полномочий.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом, Вы должны быть администратором группы</remarks>
        /// <param name="groupId">Идентификатор сообщества (указывается без знака «минус»). </param>
        /// <param name="userId">Идентификатор пользователя, чьи полномочия в сообществе нужно изменить. </param>
        /// <param name="role">
        ///     Уровень полномочий. Если параметр не задан, с пользователя user_id снимаются полномочия
        ///     руководителя.
        /// </param>
        /// <param name="isContact">Отображать ли пользователя в блоке контактов сообщества. </param>
        /// <param name="contactPosition">Должность пользователя, отображаемая в блоке контактов. </param>
        /// <param name="contactPhone">Телефон пользователя, отображаемый в блоке контактов. </param>
        /// <param name="contactEmail">Email пользователя, отображаемый в блоке контактов. </param>
        /// <returns>В случае успешного выполнения возвращает true.</returns>
        public async Task<bool> EditManager(int groupId,
                                            int userId,
                                            ApiStringEnum<GroupUserRole> role = null,
                                            bool? isContact = null,
                                            string contactPosition = null,
                                            string contactPhone = null,
                                            string contactEmail = null)
        {
            var query = new ApiQuery("groups.editManager")
                .Add("group_id", groupId)
                .Add("user_id", userId)
                .Add("role", role)
                .Add("is_contact", isContact)
                .Add("contact_position", contactPosition)
                .Add("contact_phone", contactPhone)
                .Add("contact_email", contactEmail);


            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет редактировать информацию о месте группы. Для удаления информации о местоположении нужно передать только
        ///     group_id.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом Вы должны быть администратором группы</remarks>
        /// <param name="groupId">Идентификатор группы, информацию о месте которой нужно отредактировать. </param>
        /// <param name="title">Название места.</param>
        /// <param name="address">Строковой адрес места. </param>
        /// <param name="countryId">Идентификатор страны,</param>
        /// <param name="cityId">Идентификатор города</param>
        /// <param name="latitude">Географическая широта отметки, заданная в градусах (от -90 до 90). </param>
        /// <param name="longitude">Географическая долгота отметки, заданная в градусах (от -180 до 180). </param>
        public async Task<bool> EditPlace(int groupId,
                                          string title = null,
                                          string address = null,
                                          int? countryId = null,
                                          int? cityId = null,
                                          double? latitude = null,
                                          double? longitude = null)
        {
            var query = new ApiQuery("groups.editPlace")
                .Add("group_id", groupId)
                .Add("title", title)
                .Add("address", address)
                .Add("country_id", countryId)
                .Add("city_id", cityId)
                .Add("latitude", latitude)
                .Add("longitude", longitude);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool("success").Value;
        }

        /// <summary>
        ///     Возвращает список групп указанного пользователя.
        /// </summary>
        /// <param name="userId">
        ///     ID пользователя, группы которого необходимо получить. По умолчанию выбираются группы текущего
        ///     пользователя.
        /// </param>
        /// <param name="filter">
        ///     Список фильтров сообществ, которые необходимо вернуть, перечисленные через запятую. Доступны значения admin,
        ///     groups, publics, events. По умолчанию возвращаются все сообщества пользователя.
        ///     При указании фильтра admin будут возвращены администрируемые пользователем сообщества.
        /// </param>
        /// <param name="offset">Смещение, необходимое для выборки определённого подмножества групп.</param>
        /// <param name="count">Количество записей которое необходимо вернуть, не более 1000.</param>
        public async Task<ListCount<int>> Get(int? userId = null,
                                              GroupsFilter filter = null,
                                              int? offset = null,
                                              int? count = null)
        {
            var query = new ApiQuery("groups.get")
                .Add("user_id", userId)
                .Add("extended", 0)
                .Add("filter", filter.Value)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ListCount<int>(result.Int("count").Value,
                                            result.SelectNodes("items/*")
                                                  .Select(x => x.Int())
                                                  .Where(x => x.HasValue)
                                                  .Select(x => x.Value)
                                                  .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список забаненных пользователей в сообществе.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества черного списка. </param>
        /// <param name="count">Количество записей, которое необходимо вернуть. </param>
        /// <param name="fields">
        ///     Cписок дополнительных полей, которые необходимо вернуть.
        ///     Доступные значения: sex, bdate, city, country, photo_50, photo_100, photo_200_orig, photo_200, photo_400_orig,
        ///     photo_max, photo_max_orig, online, online_mobile, lists, domain, has_mobile, contacts, connections, site,
        ///     education, universities, schools, can_post, can_see_all_posts, can_see_audio, can_write_private_message, status,
        ///     last_seen, common_count, relation, relatives, counters
        /// </param>
        /// <param name="userId">
        ///     Идентификатор пользователя, который можно передать для получения статуса бана отдельного
        ///     пользователя.
        /// </param>
        public async Task<ListCount<BannedUser>> GetBanned(int groupId,
                                                           int? offset = null,
                                                           int? count = null,
                                                           IEnumerable<string> fields = null,
                                                           int? userId = null)
        {
            var query = new ApiQuery("groups.getBanned")
                .Add("group_id", groupId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("fields", fields)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ListCount<BannedUser>(result.Int("count").Value,
                                                   result.SelectNodes("items/*").Select(x => new BannedUser(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает информацию о заданной группе или о нескольких группах.
        /// </summary>
        /// <param name="groupIds">
        ///     ID групп, перечисленные через запятую, информацию о которых необходимо получить. В качестве ID могут
        ///     быть использованы короткие имена групп. Максимум 500 групп.
        /// </param>
        /// <param name="groupId">Идентификатор или короткое имя сообщества.</param>
        /// <param name="fields">Список полей из информации о группах, которые необходимо получить.</param>
        public async Task<List<Group>> GetById(IEnumerable<string> groupIds = null,
                                               string groupId = null,
                                               IEnumerable<string> fields = null)
        {
            if (groupIds == null && groupId == null ||
                groupIds != null && groupId != null)
            {
                throw new ArgumentException("You can't specify both the groupIds and groupId paramaters simultaneously");
            }

            var query = new ApiQuery("groups.getById", true)
                .
                Add("fields", fields)
                .Add("group_ids", groupIds);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? result.SelectNodes("//group").Select(x => new Group(x)).ToList()
                       : null;
        }

        /// <summary>
        ///     Позволяет получить строку, необходимую для подтверждения адреса сервера в Callback API.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        /// <returns>
        ///     Возвращает строку, которую необходимо использовать в качестве ответа на уведомление с типом "confirmation" для
        ///     подтверждения адреса сервера в Callback API.
        /// </returns>
        public async Task<string> GetCallbackConfirmationCode(int groupId)
        {
            var query = new ApiQuery("groups.getCallbackConfirmationCode")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? result.String() : null;
        }

        /// <summary>
        ///     Позволяет получить информацию о настройках сервера для получения уведомлений Callback API в сообществе.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        public async Task<CallbackServerSettings> GetCallbackServerSettings(int groupId)
        {
            var query = new ApiQuery("groups.getCallbackServerSettings")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new CallbackServerSettings(result) : null;
        }

        /// <summary>
        ///     Позволяет получить настройки уведомлений Callback API для сообщества.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        public async Task<CallbackSettings> GetCallbackSettings(int groupId)
        {
            var query = new ApiQuery("groups.getCallbackSettings")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new CallbackSettings(result) : null;
        }

        /// <summary>
        ///     Возвращает список сообществ выбранной категории каталога.
        /// </summary>
        /// <param name="categoryId">Идентификатор категории, полученный в методе groups.getCatalogInfo. </param>
        /// <param name="subcategoryId">Идентификатор подкатегории, полученный в методе groups.getCatalogInfo. </param>
        public async Task<ListCount<Group>> GetCatalog(int? categoryId = null, int? subcategoryId = null)
        {
            var query = new ApiQuery("groups.getCatalog")
                .Add("category_id", categoryId)
                .Add("subcategory_id", subcategoryId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<Group>(result.Int("count").Value,
                                              result.SelectNodes("items/*").Select(x => new Group(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список категорий для каталога сообществ.
        /// </summary>
        /// <param name="extended">true - вернуть информацию о количестве сообществ в категории и три сообщества для предпросмотра. </param>
        /// <param name="subcategories">true - вернуть информацию об подкатегориях. </param>
        /// <returns>
        ///     После успешного выполнения возвращает список categories объектов — категорий товаров (или null если enabled =
        ///     0).
        /// </returns>
        public async Task<List<CatalogCategory>> GetCatalogInfo(bool? extended = null, bool? subcategories = null)
        {
            var query = new ApiQuery("groups.getCatalogInfo")
                .Add("extended", extended)
                .Add("subcategories", subcategories);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool("enabled").Value
                       ? result.SelectNodes("categories/*").Select(x => new CatalogCategory(x)).ToList()
                       : null;
        }

        /// <summary>
        ///     Возвращает список групп указанного пользователя. (Полная информация о группах пользователя)
        /// </summary>
        /// <param name="userId">
        ///     ID пользователя, группы которого необходимо получить. По умолчанию выбираются группы текущего
        ///     пользователя.
        /// </param>
        /// <param name="filter">
        ///     Список фильтров сообществ, которые необходимо вернуть, перечисленные через запятую. Доступны значения admin,
        ///     groups, publics, events. По умолчанию возвращаются все сообщества пользователя.
        ///     При указании фильтра admin будут возвращены администрируемые пользователем сообщества.
        /// </param>
        /// <param name="fields">Список полей из информации о группах, которые необходимо получить.</param>
        /// <param name="offset">Смещение, необходимое для выборки определённого подмножества групп.</param>
        /// <param name="count">Количество записей которое необходимо вернуть, не более 1000.</param>
        public async Task<ListCount<Group>> GetExtended(int? userId = null,
                                                        GroupsFilter filter = null,
                                                        IEnumerable<string> fields = null,
                                                        int? offset = null,
                                                        int? count = null)
        {
            var query = new ApiQuery("groups.get")
                .Add("user_id", userId)
                .Add("extended", 1)
                .Add("filter", filter.Value)
                .Add("fields", fields)
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ListCount<Group>(result.Int("count").Value,
                                              result.SelectNodes("items/*")
                                                    .Select(x => new Group(x))
                                                    .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список пользователей, которые были приглашены в группу.
        /// </summary>
        /// <param name="groupId">Идентификатор группы, список приглашенных в которую пользователей нужно вернуть. </param>
        /// <param name="offset">Смещение, необходимое для выборки определённого подмножества пользователей. </param>
        /// <param name="count">Количество пользователей, информацию о которых нужно вернуть. </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        /// <param name="nameCase">Падеж для склонения имени и фамилии пользователя. </param>
        /// <returns>Возвращает список объектов пользователей.</returns>
        public async Task<ListCount<User>> GetInvitedUsers(int groupId,
                                                           int? offset = null,
                                                           int? count = null,
                                                           IEnumerable<string> fields = null,
                                                           UserNameCase nameCase = null)
        {
            var query = new ApiQuery("groups.getInvitedUsers")
                .Add("group_id", groupId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("fields", fields)
                .Add("name_case", nameCase);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed
                       ? new ListCount<User>(result.Int("count").Value,
                                             result.SelectNodes("/items/*").Select(x => new User(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Данный метод возвращает список приглашений в сообщества и встречи.
        /// </summary>
        /// <param name="count">Количество приглашений, которое необходимо вернуть.</param>
        /// <param name="offset">Смещение, необходимое для выборки определённого подмножества приглашений.</param>
        /// <returns>Возвращает информацию о сообществах, приглашения в которые были высланы текущему пользователю. </returns>
        public async Task<ListCount<GroupInvitedBy>> GetInvites(int? count = null, int? offset = null)
        {
            var query = new ApiQuery("groups.getInvites")
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);

            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("items/*");
                var nodesCount = result.SelectNode("count");
                if (nodes != null && nodes.Any() && nodesCount != null)
                {
                    return new ListCount<GroupInvitedBy>(nodesCount.Int().Value,
                                                         nodes
                                                             .Select(x => new GroupInvitedBy(x))
                                                             .ToList());
                }
            }
            return null;
        }

        /// <summary>
        ///     Возвращает список участников сообщества.
        /// </summary>
        /// <param name="groupId">Идентификатор или короткое имя сообщества. </param>
        /// <param name="sort">Сортировка, с которой необходимо вернуть список участников.</param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества участников. По умолчанию 0. </param>
        /// <param name="count">Количество участников сообщества, информацию о которых необходимо получить. </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        /// <param name="filter">Фильтр участников сообщества</param>
        public async Task<ListCount<int>> GetMembers(int groupId,
                                                     GroupsMembersSortOrder sort = null,
                                                     IEnumerable<string> fields = null,
                                                     int? offset = null,
                                                     int? count = null,
                                                     MembersFilter filter = null)
        {
            var query = new ApiQuery("groups.getMembers", true)
                .Add("group_id", groupId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("sort", sort.StringValue)
                .Add("fields", fields)
                .Add("filter", filter.Value);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ListCount<int>(result.Int("count").Value,
                                            result.SelectNodes("items/*")
                                                  .Select(x => x.Int())
                                                  .Where(x => x.HasValue)
                                                  .Select(x => x.Value)
                                                  .ToList())
                       : null;
        }

        /// <summary>
        ///     Возвращает список заявок на вступление в сообщество.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом Вы должны быть руководителем группы</remarks>
        /// <param name="groupId">Идентификатор сообщества (указывается без знака «минус»). </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества результатов. По умолчанию — 0. </param>
        /// <param name="count">Число результатов, которые необходимо вернуть. def = 20, max = 200</param>
        /// <param name="fields">Список дополнительных полей профилей, которые необходимо вернуть.</param>
        /// <returns>
        ///     Возвращает список идентификаторов пользователей, отправивших заявки на вступление в сообщество.
        ///     Если было передано значение в параметре fields, возвращается список объектов пользователей.
        /// </returns>
        public async Task<ListCount<User>> GetRequests(int groupId,
                                                       int? offset = null,
                                                       int? count = null,
                                                       IEnumerable<string> fields = null)
        {
            var query = new ApiQuery("groups.getRequests")
                .Add("group_id", groupId)
                .Add("offset", offset)
                .Add("count", count)
                .Add("fields", fields);

            var result = await _api.Manager.Execute(query);
            var responseCount = result.Int("count").Value;
            var nodes = result.SelectNodes("items/*");
            return fields != null
                       ? new ListCount<User>(responseCount, nodes.Select(x => new User(x)).ToList())
                       : new ListCount<User>(responseCount, nodes.Select(x => new User(x.Int())).ToList());
        }

        /// <summary>
        ///     Позволяет получать данные, необходимые для отображения страницы редактирования данных сообщества.
        /// </summary>
        /// <remarks>Чтобы использовать этот метод, Вы должны быть администратором сообщества</remarks>
        /// <param name="groupId">Идентификатор сообщества, данные которого необходимо получить. </param>
        /// <returns>
        ///     В случае успешного выполнения метод вернет объект, содержащий данные сообщества, которые позволят отобразить
        ///     форму редактирования для метода groups.edit.
        /// </returns>
        public async Task<GroupSettings> GetSettings(int groupId)
        {
            var query = new ApiQuery("groups.getSettings")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new GroupSettings(result) : null;
        }

        /// <summary>
        ///     Позволяет приглашать друзей в группу.
        /// </summary>
        /// <param name="groupId">Идентификатор группы, в которую необходимо выслать приглашение </param>
        /// <param name="userId">Идентификатор пользователя, которому необходимо выслать приглашение </param>
        /// <returns>В случае успешного выполнения возвращает true.</returns>
        public async Task<bool> Invite(int groupId, int userId)
        {
            var query = new ApiQuery("groups.invite")
                .Add("group_id", groupId)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Возвращает информацию о том является ли пользователь участником заданной группы.
        /// </summary>
        /// <param name="groupId">ID или короткое имя группы.</param>
        /// <param name="userIds">Идентификаторы пользователей, не более 500. </param>
        public async Task<HashSet<int>> IsMember(int groupId, IEnumerable<int> userIds = null)
        {
            var query = new ApiQuery("groups.isMember", true)
                .Add("group_id", groupId)
                .Add("user_ids", userIds)
                .Add("extended", false);

            var result = await _api.Manager.Execute(query);
            if (!_api.Manager.MethodSuccessed)
                return null;

            var set = new HashSet<int>();
            foreach (var member in result.ChildNodes.Where(x => x.Bool("member").Value))
            {
                set.Add(member.Int("user_id").Value);
            }
            return set;
        }

        /// <summary>
        ///     Возвращает информацию о том является ли пользователь участником заданной группы.
        /// </summary>
        /// <param name="groupId">ID или короткое имя группы.</param>
        /// <param name="userId">ID пользователя. По умолчанию ID текущего пользователя.</param>
        public async Task<GroupMemberInfo> IsMemberExtended(int groupId, int? userId = null)
        {
            var query = new ApiQuery("groups.isMember", true)
                .Add("group_id", groupId)
                .Add("user_id", userId)
                .Add("extended", 1);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new GroupMemberInfo(result) : null;
        }

        /// <summary>
        ///     Данный метод позволяет вступить в группу, публичную страницу, а также подтверждать об участии во встрече.
        /// </summary>
        /// <param name="groupId">Идентификатор группы, публичной страницы или встречи.</param>
        /// <param name="notSure">
        ///     Опциональный параметр учитываемый, если groupId принадлежит встрече. true - Возможно пойду. false -
        ///     Точно пойду. По умолчанию false.
        /// </param>
        /// <returns>В случае успешного вступления в группу метод вернёт true. </returns>
        public async Task<bool> Join(int groupId, bool? notSure = null)
        {
            var query = new ApiQuery("groups.join")
                .Add("group_id", groupId)
                .Add("not_sure", notSure);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Данный метод позволяет выходить из группы, публичной страницы, или встречи.
        /// </summary>
        /// <param name="groupId">Идентификатор группы, публичной страницы или встречи.</param>
        /// <returns>В случае успешного вступления в группу метод вернёт true. </returns>
        public async Task<bool> Leave(int groupId)
        {
            var query = new ApiQuery("groups.leave")
                .Add("group_id", groupId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет исключить пользователя из группы или отклонить заявку на вступление.
        /// </summary>
        /// <remarks>Для того, чтобы воспользоваться этим методом Вы должны быть администратором группы.</remarks>
        /// <param name="groupId">Идентификатор группы, из которой необходимо исключить пользователя. </param>
        /// <param name="userId">Идентификатор пользователя, которого нужно исключить. </param>
        /// <returns>В случае успешного выполнения возвращает true</returns>
        public async Task<bool> RemoveUser(int groupId, int userId)
        {
            var query = new ApiQuery("groups.removeUser")
                .Add("group_id", groupId)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Позволяет менять местоположение ссылки в списке.
        ///     Список ссылок может быть получен методом groups.getById, с параметром fields=links.
        /// </summary>
        /// <param name="groupId">Идентификатор группы, внутри которой перемещается ссылка </param>
        /// <param name="linkId">Идентификатор ссылки, которую необходимо переместить </param>
        /// <param name="after">
        ///     Идентификатор ссылки после которой необходимо разместить перемещаемую ссылку. 0 – если ссылку нужно
        ///     разместить в начале списка.
        /// </param>
        /// <returns>В случае успешного выполнения возвращает true</returns>
        public async Task<bool> ReorderLink(int groupId, int linkId, int after)
        {
            var query = new ApiQuery("groups.reorderLink")
                .Add("group_id", groupId)
                .Add("link_id", linkId)
                .Add("after", after);

            var result = await _api.Manager.Execute(query);
            var boolean = result.Bool();
            return boolean ?? false;
        }

        /// <summary>
        ///     Осуществляет поиск групп по заданной подстроке.
        /// </summary>
        /// <param name="q">Поисковый запрос по которому необходимо найти группу.</param>
        /// <param name="offset">Смещение, необходимое для выборки определённого подмножества результатов поиска.</param>
        /// <param name="count">Количество результатов поиска которое необходимо вернуть.</param>
        /// <param name="sort">Порядок сортировки</param>
        /// <param name="cityId">Идентификатор города. При передаче этого параметра поле country_id игнорируется. </param>
        /// <param name="countryId">Идентификатор страны.</param>
        /// <param name="future">
        ///     При передаче значения true будут выведены предстоящие события. Учитывается только при передаче в
        ///     качестве type значения event.
        /// </param>
        /// <param name="type">Тип сообщества. Возможные значения: group, page, event.</param>
        public async Task<ListCount<Group>> Search(string q,
                                                   GroupsSortOrder sort = null,
                                                   int? countryId = null,
                                                   int? cityId = null,
                                                   string type = null,
                                                   int? offset = null,
                                                   int? count = null,
                                                   bool? future = null,
                                                   bool? market = null)
        {
            var query = new ApiQuery("groups.search")
                .Add("q", q)
                .Add("offset", offset)
                .Add("count", count)
                .Add("sort", sort.Value)
                .Add("country_id", countryId)
                .Add("city_id", cityId)
                .Add("future", future)
                .Add("market", market)
                .Add("type", type);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed
                       ? new ListCount<Group>(result.Int("count").Value,
                                              result.SelectNodes("//group").Select(x => new Group(x)).ToList())
                       : null;
        }

        /// <summary>
        ///     Позволяет задать настройки сервера для получения уведомлений Callback API.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        /// <param name="secretKey">Секретный ключ. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> SetCallbackSettings(int groupId, string secretKey = null)
        {
            var query = new ApiQuery("groups.setCallbackSettings")
                .Add("group_id", groupId)
                .Add("secret_key", secretKey);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Убирает пользователя из черного списка сообщества.
        /// </summary>
        /// <param name="groupId">Идентификатор сообщества. </param>
        /// <param name="userId">Идентификатор пользователя, которого нужно убрать из черного списка. </param>
        /// <returns>После успешного выполнения возвращает true.</returns>
        public async Task<bool> UnbanUser(int groupId, int userId)
        {
            var query = new ApiQuery("groups.unbanUser")
                .Add("group_id", groupId)
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);

            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }
    }
}